"""
Interfaces to deal with different WSGI complient servers. To write your own 
handler extend the HandlerBase and you should be able to overwrite the start 
method and all the options will be availible in self. 
If you wish to daemonize use super(WSGIHandler, self).start(daemonize) at the
beginning of your script
"""

import signal, sys, os, atexit, time
from django.core.handlers import wsgi
import cPickle as pickle

class HandlerBase(object):
    """
    Abstract base to write handlers for different servers
    
    __init__ needs to have
    @option host, 
    @option port or host with a port,
    @option workdir (generally should be '/')
    @option pidfile ('/tmp/port-authority.pkl') and 
    @option umask 
    
    This creates a HandleBase object which can pickled and recover/monitor
    processes
    """
    def __init__(self, *args, **kwargs):
        self.application = wsgi.WSGIHandler()
        self.workdir = kwargs.get('workdir', '/')
        self.host = kwargs.get('host', 'localhost')
        self.port = False
        if ':' in self.host:
            self.host, self.port = self.host.split(':', 1)
        self.port = kwargs.get('port', self.port or 8000)
        self.umask = kwargs.get('umask', 022)
        self.pidfile = kwargs.get('pidfile', '/tmp/port-authority.pkl')
        self.group = kwargs.get('group', None)
        self.user = kwargs.get('user', None)
        self.stdin = kwargs.get('stdin', None)
        self.stdout = kwargs.get('stdout', None)
        self.stderr = kwargs.get('stderr', None)
        if self.group: #this needs to happen 1st
            try:
                os.setregid(int(self.group), int(self.group))
            except OSError, e:
                print "Can't set group: %s\n" % e
                raise
        if self.user:
            try:
                os.setreuid(int(self.user), int(self.user))
            except OSError, e:
                print "Can't set user: %s\n" % e
                raise
        os.umask(int(self.umask))
        # flag to indicate whether the process is running or not
        self.started = False
        try:
            self.processes = pickle.load(open(self.pidfile, 'r'))
        except IOError, error:
            if 'Permission' in error.strerror:
                print "Permission denied for %s.\n" % self.pidfile
                raise
            else:
                self.processes = []
        self.processes.append(self)

    def start(self, daemonize=True):
        """
        Start the daemon
        
        @option daemonize Boolean 
        """
        if daemonize: self._daemonize(self.workdir)
    
    def _daemonize(self, workdir):
        """
        do the UNIX double-fork magic, apparently see Stevens' "Advanced 
        Programming in the UNIX Environment" for details (ISBN 0201563177)
        """
        try: 
            if os.fork():
                # exit first parent
                sys.exit(0)
        except OSError, e: 
            sys.exit(1)
    
        # decouple from parent environment
        os.chdir(workdir) 
        os.setsid()
    
        # do second fork
        try: 
            if os.fork():
                # exit from second parent
                sys.exit(0) 
        except OSError, e: 
            sys.exit(1) 
        
        # NOW I AM A DAEMON!!!
        
        # redirect standard file descriptors
        sys.stdout.flush()
        sys.stderr.flush()
        
        try:        
            in_file = open(self.stdin, 'r')
        except IOError:
            # oops no stdin file
            in_file = open(os.devnull, 'r')

        os.dup2(in_file.fileno(), sys.stdin.fileno()) 
        out_log = open(self.stdout, 'a+')
        error_log = open(self.stderr, 'a+', 0)
        
        os.dup2(out_log.fileno(), sys.stdout.fileno())
        os.dup2(error_log.fileno(), sys.stderr.fileno())
        
        sys.stdout, sys.stderr = out_log, error_log
        
        # write pidfile
        self.pid = os.getpid()
        pickle.dump(self.processes, open(self.pidfile, 'w'))
        atexit.register(self._del_ps)
        
    
    def _del_ps(self):
        self.processes.remove(self)
        if not len(self.processes):
            os.remove(self.pidfile)
        else:
            pickle.dump(self.processes, open(self.pidfile, 'w'))

        
    def stop(self):
        """
        Stop the daemon
        """
        # Get the pid from the pidfile
        try:
            p_list = pickle.load(open(self.pidfile,'r'))
        except IOError:
            p_list = []
        real_object = self.match_handler(p_list)
        if not real_object:
            message = "Process not found in %s. Daemon not running?\n" % self.pidfile
            sys.stdout.write(message)
            return # not an error in a restart
        # Try killing the daemon process
        try:
            while 1:
                os.kill(real_object.pid, signal.SIGTERM)
                time.sleep(0.1)
        except OSError, error:
            if "No such process" in error.strerror:
                real_object._del_ps() # use the real object because this objects processes in not right
            else:
                sys.exit(1)

    def restart(self):
        self.stop()
        self.start()

    def match_handler(self, process_list):
        """
        Checks against host and port (assuming they are unique) against 
        BaseHandler List
        """
        for ps in process_list:
            if self.port == ps.port:
                if self.host == ps.host:
                    return ps
        else:
            return None